# zk_service.py
import socket
import struct
import threading
import time
from datetime import datetime
import os
import pythoncom
import win32com.client
from typing import List, Dict, Optional, Tuple
import logging

# 设置日志
logger = logging.getLogger(__name__)

class ZKDevice:
    """中控门禁设备 - 使用COM组件版本"""

    def __init__(self, ip: str, port: int = 4370, protocol: str = 'TCP',
                 password: str = None, machine_number: int = 1, timeout: int = 10):
        self.ip = ip
        self.port = port
        self.protocol = protocol.upper()
        self.password = password
        self.machine_number = machine_number
        self.timeout = timeout
        self.connected = False
        self._lock = threading.RLock()
        self.zk = None

    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()

    def _check_network(self) -> bool:
        """检查网络连通性"""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.settimeout(3)
                return sock.connect_ex((self.ip, self.port)) == 0
        except:
            return False

    def connect(self) -> bool:
        """连接到设备"""
        with self._lock:
            if self.connected:
                return True

            try:
                # 确保COM初始化
                pythoncom.CoInitialize()

                # 创建COM对象
                try:
                    self.zk = win32com.client.Dispatch("zkemkeeper.ZKEM")
                    if not self.zk:
                        logger.error(f"创建COM对象失败: zkemkeeper.ZKEM")
                        return False
                except pythoncom.com_error as e:
                    logger.error(f"创建COM对象异常: {str(e)}")
                    return False

                # 检查网络连通性
                if not self._check_network():
                    logger.error(f"网络连接失败: {self.ip}:{self.port}")
                    return False

                # 连接设备
                if not self.zk.Connect_Net(self.ip, self.port):
                    error_code = self.zk.GetLastError()
                    logger.error(f"设备连接失败，错误码: {error_code}, 设备: {self.ip}")
                    return False

                # 验证设备是否真正连接
                if not hasattr(self.zk, 'GetSerialNumber'):
                    logger.error(f"设备连接验证失败: 缺少必要方法")
                    self.zk.Disconnect()
                    return False

                self.connected = True
                logger.info(f"设备 {self.ip} 连接成功")
                self._initialize_device()
                return True

            except Exception as e:
                logger.error(f"连接设备 {self.ip} 时发生异常: {str(e)}")
                if hasattr(self, 'zk') and self.zk:
                    try:
                        self.zk.Disconnect()
                    except:
                        pass
                return False

    def _initialize_device(self):
        """设备初始化操作"""
        try:
            # 设置通信密码（如果有）
            if self.password:
                try:
                    password_int = int(self.password)
                    if not self.zk.SetCommPassword(password_int):
                        logger.warning(f"设置通讯密码失败，设备: {self.ip}")
                except (ValueError, TypeError):
                    logger.warning(f"通讯密码 '{self.password}' 无效，已忽略")

            # 注册连接事件（如果需要）
            if hasattr(self.zk, 'RegEvent'):
                # 注册通用事件
                self.zk.RegEvent(self.machine_number, 65535)  # 所有事件

            # 获取设备信息
            self._get_device_info()

        except Exception as e:
            logger.warning(f"设备初始化操作失败: {str(e)}")

    def _get_device_info(self):
        """获取设备信息"""
        try:
            # 获取设备序列号
            if hasattr(self.zk, 'GetSerialNumber'):
                serial_number = self.zk.GetSerialNumber(self.machine_number)
                logger.info(f"设备序列号: {serial_number}, 设备: {self.ip}")

            # 获取设备版本
            if hasattr(self.zk, 'GetDeviceVersion'):
                version = self.zk.GetDeviceVersion(self.machine_number)
                logger.info(f"设备版本: {version}, 设备: {self.ip}")

            # 获取设备IP地址
            if hasattr(self.zk, 'GetDeviceIP'):
                device_ip = self.zk.GetDeviceIP(self.machine_number)
                logger.info(f"设备IP: {device_ip}, 设备: {self.ip}")

        except Exception as e:
            logger.warning(f"获取设备信息失败: {str(e)}")

    def disconnect(self):
        """断开连接"""
        with self._lock:
            if self.connected and self.zk:
                try:
                    self.zk.Disconnect()
                    logger.info(f"设备 {self.ip} 断开连接")
                except Exception as e:
                    logger.error(f"断开设备 {self.ip} 连接时发生异常: {str(e)}")
                finally:
                    self.zk = None
                    self.connected = False
                    try:
                        pythoncom.CoUninitialize()
                    except:
                        pass

    def _get_variant_value(self, variant):
        """安全获取VARIANT的值"""
        try:
            if variant is None:
                return None

            # 处理win32com VARIANT类型
            if hasattr(variant, '_oleobj_'):
                if hasattr(variant, 'value'):
                    return variant.value
                elif hasattr(variant, 'Value'):
                    return variant.Value
                else:
                    return variant
            # 处理普通Python类型
            elif isinstance(variant, (int, float, str, bytes)):
                return variant
            # 处理其他情况
            else:
                try:
                    return int(variant)
                except (ValueError, TypeError):
                    try:
                        return str(variant)
                    except:
                        return variant
        except Exception as e:
            logger.warning(f"获取VARIANT值失败: {str(e)}")
            return variant

    def get_device_time(self) -> Tuple[bool, datetime]:
        """获取设备时间 - 修复版本"""
        if not self.connected and not self.connect():
            return False, None

        try:
            # 创建VARIANT变量
            year = win32com.client.VARIANT(win32com.client.pythoncom.VT_BYREF | win32com.client.pythoncom.VT_I4, 0)
            month = win32com.client.VARIANT(win32com.client.pythoncom.VT_BYREF | win32com.client.pythoncom.VT_I4, 0)
            day = win32com.client.VARIANT(win32com.client.pythoncom.VT_BYREF | win32com.client.pythoncom.VT_I4, 0)
            hour = win32com.client.VARIANT(win32com.client.pythoncom.VT_BYREF | win32com.client.pythoncom.VT_I4, 0)
            minute = win32com.client.VARIANT(win32com.client.pythoncom.VT_BYREF | win32com.client.pythoncom.VT_I4, 0)
            second = win32com.client.VARIANT(win32com.client.pythoncom.VT_BYREF | win32com.client.pythoncom.VT_I4, 0)

            # 调用设备时间获取方法
            ret = self.zk.GetDeviceTime(
                self.machine_number,
                year,
                month,
                day,
                hour,
                minute,
                second
            )

            if ret:
                # 安全获取VARIANT的值
                year_val = self._get_variant_value(year)
                month_val = self._get_variant_value(month)
                day_val = self._get_variant_value(day)
                hour_val = self._get_variant_value(hour)
                minute_val = self._get_variant_value(minute)
                second_val = self._get_variant_value(second)

                # 验证时间值有效性
                if year_val == 0 or month_val == 0 or day_val == 0:
                    logger.error(f"获取到无效设备时间: {year_val}-{month_val}-{day_val}")
                    return False, None

                try:
                    device_time = datetime(
                        int(year_val),
                        int(month_val),
                        int(day_val),
                        int(hour_val),
                        int(minute_val),
                        int(second_val)
                    )
                    logger.info(f"成功获取设备时间: {device_time}, 设备: {self.ip}")
                    return True, device_time
                except ValueError as e:
                    logger.error(f"设备时间格式无效: {str(e)}, 设备: {self.ip}")
                    return False, None
            return False, None
        except Exception as e:
            logger.error(f"获取设备时间异常: {str(e)}, 设备: {self.ip}")
            return False, None

    def open_door(self, door_id: int = 1) -> bool:
        """远程开门"""
        if not 1 <= door_id <= 4:
            raise ValueError(f"门ID必须在1-4之间，当前值: {door_id}")

        if not self.connected and not self.connect():
            return False

        try:
            # 启用设备
            self.zk.EnableDevice(self.machine_number, True)

            # 远程开门
            result = self.zk.ACUnlock(self.machine_number, 10)  # 10秒延时

            if result:
                # 播放语音提示
                self.zk.PlayVoiceByIndex(9)  # 播放"谢谢"
                logger.info(f"门 {door_id} 开门成功，设备: {self.ip}")
            else:
                error_code = self.zk.GetLastError()
                logger.error(f"开门失败，错误码: {error_code}, 设备: {self.ip}, 门: {door_id}")

            # 禁用设备
            self.zk.EnableDevice(self.machine_number, False)

            return result

        except Exception as e:
            logger.error(f"开门操作异常: {str(e)}, 设备: {self.ip}, 门: {door_id}")
            return False

    def restart_device(self) -> bool:
        """重启设备"""
        if not self.connected and not self.connect():
            return False

        try:
            result = self.zk.RestartDevice(self.machine_number)
            if result:
                logger.info(f"设备重启命令发送成功: {self.ip}")
            else:
                error_code = self.zk.GetLastError()
                logger.error(f"重启失败，错误码: {error_code}, 设备: {self.ip}")
            return result
        except Exception as e:
            logger.error(f"重启操作异常: {str(e)}, 设备: {self.ip}")
            return False


    def set_user(self, user_id: int, name: str, card_number: str = None, privilege: int = 0) -> bool:
        """设置用户信息到设备"""
        if not self.connected and not self.connect():
            return False

        try:
            # 启用设备
            self.zk.EnableDevice(self.machine_number, True)

            # 设置用户信息
            result = self.zk.SSR_SetUserInfo(
                self.machine_number,
                str(user_id),
                name,
                "",  # 密码
                privilege,
                True  # 启用
            )

            if result and card_number:
                # 设置卡号
                result = self.zk.SSR_SetUserCard(
                    self.machine_number,
                    str(user_id),
                    card_number,
                    0,  # 卡有效期
                    True  # 启用
                )

            # 禁用设备
            self.zk.EnableDevice(self.machine_number, False)

            if result:
                logger.info(f"用户设置成功: {name}({user_id}), 设备: {self.ip}")
            else:
                error_code = self.zk.GetLastError()
                logger.error(f"用户设置失败，错误码: {error_code}, 用户: {name}({user_id}), 设备: {self.ip}")

            return result

        except Exception as e:
            logger.error(f"设置用户信息异常: {str(e)}, 用户: {name}({user_id}), 设备: {self.ip}")
            return False

    def test_connection(self) -> Tuple[bool, str]:
        """测试连接"""
        try:
            if self.connect():
                # 进一步验证设备是否真正响应
                try:
                    # 尝试获取设备时间
                    time_success, device_time = self.get_device_time()
                    if time_success:
                        return True, f"设备连接成功且响应正常，设备时间: {device_time}"
                    else:
                        return True, "设备连接成功但无法获取时间"
                except Exception as e:
                    return False, f"设备连接但无响应: {str(e)}"
            else:
                return False, "设备连接失败"
        except Exception as e:
            return False, f"连接测试异常: {str(e)}"


class ZKDeviceManager:
    """设备连接管理器"""

    def __init__(self, max_connections: int = 10, connection_timeout: int = 30):
        self.devices = {}  # 设备连接池
        self.lock = threading.RLock()
        self.max_connections = max_connections
        self.connection_timeout = connection_timeout
        self.last_cleanup_time = time.time()

    def get_device(self, device_id: int, ip: str, port: int = 4370,
                   protocol: str = 'TCP', password: str = None) -> ZKDevice:
        """获取设备实例"""
        with self.lock:
            # 定期清理过期连接
            current_time = time.time()
            if current_time - self.last_cleanup_time > 300:  # 每5分钟清理一次
                self._cleanup_connections()
                self.last_cleanup_time = current_time

            # 尝试从连接池获取
            if device_id in self.devices:
                device, last_used = self.devices[device_id]
                if current_time - last_used < self.connection_timeout:
                    # 更新最后使用时间
                    self.devices[device_id] = (device, current_time)
                    return device

            # 创建新连接
            if len(self.devices) >= self.max_connections:
                self._cleanup_connections(force=True)

            device = ZKDevice(ip, port, protocol, password, device_id)
            self.devices[device_id] = (device, current_time)
            return device

    def _cleanup_connections(self, force: bool = False):
        """清理过期连接"""
        current_time = time.time()
        devices_to_remove = []

        for device_id, device_info in self.devices.items():
            device, last_used = device_info
            # 如果强制清理或连接过期(5分钟未使用)
            if force or current_time - last_used > 300:
                try:
                    device.disconnect()
                    devices_to_remove.append(device_id)
                except Exception as e:
                    logger.error(f"清理设备连接时出错: {str(e)}")
                    continue

        for device_id in devices_to_remove:
            try:
                del self.devices[device_id]
            except KeyError:
                pass

    def release_device(self, device_id: int):
        """释放设备连接"""
        with self.lock:
            if device_id in self.devices:
                device, _ = self.devices[device_id]
                device.disconnect()
                del self.devices[device_id]